﻿using FySystem.AssistantServices;
using FySystem.Net.SimSocket.Protocals;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static FySystem.Net.SimSocket.Protocals.HandlerBase;

namespace FySystem.Net.SimSocket
{
    /// <summary>
    /// 最近看了点websocket的协议，发现我们平时定的协议弱爆了，根本就是小学生水平
    /// 但websocket协议是英文的，中文的又太简略，有些地方还是不清楚，可弄明白的部分也足以衍生出一套完整的协议
    /// 这里模仿websocket的协议，自订一套通信协议，虽然达不到大学生水平，可大致也能达到初中生级别了：
    /// 步骤：
    /// 1.握手，主要是为了过滤一些非法连接，比如服务端监听10000端口，经常会收到一些莫名其妙的数据，如果对这些数据进行解析，可能会出问题，
    /// 如果没有经过握手步骤，连接就认为是非法，收到的数据仅进行握手校验，校验不通过就丢弃
    /// 握手流程：
    ///     客户端：----------------------------------------------------------------- 发送一个GUID（16进制转码，所以长度16，同时这个作为断线重连的依据），一个随机生成的Mask，两者通过异或生成的值
    ///             |    0   |    1   |    2-17    |   18-33    |   34-49    |   50    |
    ///             |  [20]  |  [08]  |    GUID    |    Mask    |   RESULT   | version |
    ///     服务端：----------------------------------------------------------------- 用相同的算法用GUID和Mask计算Result，比较和客户端发送过来的是否相同，相同则完成握手，将其标记为有效连接
    ///             |    0   |    1   |    2    |
    ///             |  [20]  |  [08]  |  [0/1]  |
    /// 2.数据帧：
    /// FIN：1表示数据帧结束，0表示后面还有数据帧，当前帧只是一条完整信息的一部分（比如传输文件的时候，当FIN为0的时候表示后面还有数据，当为1的时候表示传输完成）
    /// RSV：预留，为0
    /// opcode：数据类型，
    ///         0：普通数据帧
    ///         1：ping，客户端-->服务端
    ///         2：pong，服务端-->客户端
    ///         3：关闭连接
    /// payload length：数据长度，不包含头，如果为0-125，改数字就表示数据长度为0-125，如果为126，表示后面两字节表示数据长度，如果为127，后面4字节表示数据长度
    /// websocket数据帧中还有mask什么的，没太明白具体流程，只知道是异或运算的，这里忽略掉
    /// 由于精简掉了一些东西，同时也修改了一些协定，所以起名为Simple socket（简易websocket），简称SimSocket
    /// +----------------+----------------+-------------------------+-------------------------+
    /// |       0        |        1       |       [2-3]/[2-5]       |           [n]           |
    /// +-+-+-+-+--------+----------------+-------------------------+-------------------------+
    /// |F|R|R|R|        |                |                         |                         |
    /// |I|S|S|S| opcode | Payload length | Extended payload length |        Frame data       |
    /// |N|V|V|V|   (4)  |                |                         |                         |
    /// | |1|2|3|        |                |                         |                         |
    /// +-+-+-+-+--------+----------------+-------------------------+-------------------------+
    /// </summary>
    public class SimServer : IAssistantService
    {
        private Action<IAssistantService> _configFun;

        public void Configure(Action<IAssistantService> configFun)
        {
            _configFun = configFun;
        }

        public void InvokeConfigure()
        {
            try
            {
                _configFun?.Invoke(this);
            }
            catch (Exception ex)
            {
                FyLogContext.Write(ex.Message);
            }
        }

        #region 属性

        public int Port { get; set; } = 20000;

        public IPAddress IpAddress { get; set; } = IPAddress.Any;

        public Action<SimClient> OnAccept { get; set; }

        public Action<SimClient, FrameInfo> OnReceive { get; set; }

        public Action<Exception,SimClient> OnException { get; set; }

        /// <summary>
        /// 12小时没有进行过任何数据的交互认为连接失效，将其进行释放并
        /// </summary>
        public Action<SimClient> OnConnectionGC { get; set; }

        /// <summary>
        /// 服务器socket
        /// </summary>
        internal Socket listener { get; set; }

        /// <summary>
        /// 所有的客户端连接，超过一定时间没有收到过数据的认为失效，需要删除
        /// </summary>
        internal Dictionary<string, SimClient> clients = new Dictionary<string, SimClient>();

        #endregion

        private ManualResetEvent _allDone = new ManualResetEvent(false);

        public void Execute(IAssistantServiceContext assistantServiceContext, IAssistantService service)
        {
            try
            {
                Task task = new Task(new Action(() =>
                  {
                      IPEndPoint localEndPoint = new IPEndPoint(IpAddress, Port);
                      listener = new Socket(IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                      listener.Bind(localEndPoint);
                      listener.Listen(int.MaxValue);

                      while (true)
                      {
                          _allDone.Reset();
                          listener.BeginAccept(MyAcceptCallback, listener);
                          _allDone.WaitOne();
                      }
                  }));
                task.Start();

                Task taskGC = new Task(new Action(() =>
                {
                    ConnectionGC();
                }));
                taskGC.Start();
            }
            catch (Exception ex)
            {
                OnException?.Invoke(ex, null);
            }
        }

        /// <summary>
        /// socket的连入回调
        /// </summary>
        /// <param name="ar"></param>
        private void MyAcceptCallback(IAsyncResult ar)
        {
            SimStateObjectBase stateObjectBase = null;
            try
            {
                _allDone.Set();
                listener = (Socket)ar.AsyncState;
                Socket handler = listener.EndAccept(ar);

                stateObjectBase = new SimStateObjectBase();
                stateObjectBase.workSocket = handler;
                handler.BeginReceive(stateObjectBase.buffer, 0, SimStateObjectBase.BufferSize, 0, MyReadCallback, stateObjectBase);
            }
            catch (Exception ex)
            {
                OnException?.Invoke(ex, null);
            }
        }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        /// <param name="ar"></param>
        private void MyReadCallback(IAsyncResult ar)
        {
            SimClient simClient = null;
            try
            {
                SimStateObjectBase stateObjectBase = (SimStateObjectBase)ar.AsyncState;
                stateObjectBase.lastReceivedTime = DateTime.Now;
                int bytesRead = stateObjectBase.workSocket.EndReceive(ar);

                if (bytesRead > 0)
                {
                    if (stateObjectBase.helloFlag == false)
                    {
                        //握手数据比较短，按一条数据处理，肯定不会有粘包的情况，如果被分割成了多个包，表示网络很差，让客户端关了重连
                        HandlerBase handlerBase = null;
                        HelloInfo hello = new HelloInfo();
                        bool valResult = HandlerBase.ValidateHello(ref stateObjectBase.buffer, bytesRead, ref hello, ref handlerBase);

                        if (handlerBase != null)
                        {
                            //给客户端返回数据
                            byte[] byteResponse = handlerBase.GenHelloSCPackage(ref stateObjectBase.buffer, valResult);
                            stateObjectBase.workSocket.Send(byteResponse);

                            //创建客户端，同时将版本号记录到客户端连接
                            if (valResult == true)
                            {
                                stateObjectBase.ID = hello.Guid;

                                lock (clients)
                                {
                                    if (clients.ContainsKey(hello.Guid))
                                    {
                                        //如果是断线重连
                                        simClient = clients[hello.Guid];

                                        //先释放之前的连接
                                        if (simClient.SimStateObject != null)
                                            simClient.SimStateObject.Dispose();

                                        //将现在的连接替换之前的连接
                                        simClient.SimStateObject = stateObjectBase;
                                        simClient.SimSocketInfo.Version = hello.Version;
                                        simClient.SimSocketInfo.RemoteEndPoint = (IPEndPoint)stateObjectBase.workSocket.RemoteEndPoint;
                                        stateObjectBase.helloFlag = true;
                                    }
                                    else
                                    {
                                        //如果是新的连接
                                        simClient = new SimClient();
                                        simClient.IsConnect = true;
                                        simClient.SimStateObject = stateObjectBase;
                                        simClient.SimSocketInfo.Version = hello.Version;
                                        simClient.SimSocketInfo.ID = hello.Guid;
                                        simClient.SimSocketInfo.RemoteEndPoint = (IPEndPoint)stateObjectBase.workSocket.RemoteEndPoint;
                                        stateObjectBase.helloFlag = true;
                                        OnAccept?.Invoke(simClient);
                                        clients.Add(hello.Guid, simClient);
                                    }
                                    simClient.IsConnect = true;
                                }
                            }
                        }
                    }
                    else
                    {
                        //如果已经完成了握手
                        if (clients.ContainsKey(stateObjectBase.ID) == false)
                            return;

                        simClient = clients[stateObjectBase.ID];
                        HandlerBase handlerBase = HandlerManager.GetHandlerByVersion(simClient.SimSocketInfo.Version);
                        if (handlerBase != null)
                        {
                            List<FrameInfo> frames = null;
                            frames = handlerBase.DepackFrame(stateObjectBase, bytesRead);

                            if (frames != null)
                            {
                                foreach (FrameInfo item in frames)
                                {
                                    if (item.PackType == EPackTypes.Normal)
                                        OnReceive?.Invoke(simClient, item);
                                    else if (item.PackType == EPackTypes.Ping)
                                    {
                                        try
                                        {
                                            simClient.IsConnect = true;
                                            simClient.SendPong();
                                        }
                                        catch
                                        {
                                            simClient.IsConnect = false;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                OnException?.Invoke(ex, simClient);
            }
            finally
            {
                if (simClient != null)
                {
                    if (simClient.SimStateObject.workSocket.Connected)
                        simClient.SimStateObject.workSocket.BeginReceive(simClient.SimStateObject.buffer, 0, SimStateObjectBase.BufferSize, 0, MyReadCallback, simClient.SimStateObject);
                }
            }
        }

        /// <summary>
        /// 根据ID获取客户端连接
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public SimClient GetSimclientByID(string id)
        {
            SimClient res = null;
            try
            {
                if (clients.ContainsKey(id))
                    res = clients[id];
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 回收连接
        /// </summary>
        private void ConnectionGC()
        {
            while (true)
            {
                Thread.Sleep(60000);
                try
                {
                    DateTime now = DateTime.Now;
                    List<string> keys = new List<string>();

                    foreach (string key in clients.Keys)
                    {
                        var item = clients[key];
                        if ((now - item.SimStateObject.lastReceivedTime).Minutes >= 2)
                        {
                            //连接空闲时会进行心跳包发送，所以理论上不可能2分钟没数据交互
                            keys.Add(key);
                            item.Dispose();
                            OnConnectionGC?.Invoke(item);       //如果有回调，执行回调
                        }
                    }

                    if (keys.Count > 0)
                    {
                        lock(clients)
                        {
                            foreach(string key in keys)
                            {
                                clients.Remove(key);
                            }
                        }
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }
    }
}
